package com.redhat.redwork.validation;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.MessageInterpolator;
import javax.validation.Path;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import org.apache.struts2.interceptor.validation.SkipValidation;
import org.hibernate.validator.engine.ResourceBundleMessageInterpolator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.Preparable;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;

/**
 * 校验拦截器，集成JSR303 Bean Validation到struts2
 * 
 * @author Justin
 *
 */
public class BeanValidationInterceptor extends MethodFilterInterceptor{

	private static final long serialVersionUID = -6772018535349734097L;
	private static final Logger LOG = LoggerFactory.getLogger(BeanValidationInterceptor.class);
	
	private static ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
	// 国际化资源缓存
	private static Map<String, MessageInterpolator> localesMap = new TreeMap<String, MessageInterpolator>();
	
	@Override
	public String doIntercept(ActionInvocation invocation) throws Exception {
        
		Object action = invocation.getAction();
		Method method = action.getClass().getMethod(invocation.getProxy().getMethod(), new Class[0]);
		
		// 标记@SkipValidation的跳过验证
		if (method.isAnnotationPresent(SkipValidation.class))
			return invocation.invoke();
		
		// 由于要用到addFieldError/getText/getLocale等方法，所以只支持ActionSupport的子类
		if (!(action instanceof ActionSupport)) {
			LOG.warn("only descendents of ActionSupport were supportted, but {} is not, skip.", action.getClass());
			return invocation.invoke();
		}
		
		ActionSupport actionAs = (ActionSupport) action;
		LOG.debug("validate Action: {}", actionAs.getClass());
		
		// 获取“消息撺改器”
		Locale clientLocale = actionAs.getLocale();
		MessageInterpolator messageInterpolator = loadMessageInterpolator(clientLocale);
		
		Collection invalidValuesFromRequest = validateAction(actionAs, messageInterpolator);

		if (invalidValuesFromRequest.isEmpty()) {
			LOG.debug("no constraint violation.");
			actionAs.validate();
			if (actionAs.hasActionErrors() || actionAs.hasFieldErrors()) {
				LOG.debug("found custom validation errors: "+actionAs.getFieldErrors()+" "+actionAs.getActionErrors());
				return actionAs.input();
				
			}else {
				try{
					return invocation.invoke();
				}catch(ConstraintViolationException cve){
					LOG.debug("action throws ConstraintViolationException, process...");
					
					Set<ConstraintViolation<?>> constraintViolations = cve.getConstraintViolations();
					addToFieldErrors(actionAs, constraintViolations);

					return new ValidationResponseWriter().writeResponse(actionAs, null);
				}
			}
		} else {
			addToFieldErrors(actionAs, invalidValuesFromRequest);

			LOG.debug("Validation found {} validation Errors.", actionAs.getFieldErrors().size());
			actionAs.validate();
			if (action instanceof Preparable) {
				Method methodPrepare = Preparable.class.getDeclaredMethod("prepare");
				methodPrepare.invoke(action);
			}
			return new ValidationResponseWriter().writeResponse(actionAs, null);
		}

	}
	
	/**
	 * 将校验异常转换为Action的FieldError
	 * @param actionAs
	 * @param invalidValuesFromRequest
	 */
	public void addToFieldErrors(ActionSupport actionAs,
			Collection invalidValuesFromRequest) {
		for (ConstraintViolation constraintViolation : (List<ConstraintViolation>)invalidValuesFromRequest) {
			StringBuilder sbMessage = new StringBuilder(actionAs.getText(constraintViolation.getPropertyPath().toString(),""));
			if (sbMessage.length()>0)
				sbMessage.append(" - ");
			sbMessage.append(actionAs.getText(constraintViolation.getMessage()));
			actionAs.addFieldError(constraintViolation.getPropertyPath().toString(), sbMessage.toString());
		}
	}

	/**
	 * 
	 * @param clientLocale
	 * @return
	 */
	protected MessageInterpolator loadMessageInterpolator(Locale clientLocale){
		MessageInterpolator interpolator = null;
		
		if (localesMap.containsKey(clientLocale.toString())) {
			interpolator = localesMap.get(clientLocale.toString());
		} else {
			ResourceBundle clientDefaultMessages = ResourceBundle.getBundle("org/hibernate/validator/ValidationMessages", clientLocale);
			
			try {
				ResourceBundle clientCustomMessages = ResourceBundle.getBundle("ValidationMessages", clientLocale);
				interpolator = new ResourceBundleMessageInterpolator(clientCustomMessages);
			} catch (MissingResourceException e) {
				interpolator = new ResourceBundleMessageInterpolator(clientDefaultMessages);
			}
			localesMap.put(clientLocale.toString(), interpolator);
		}
		
		return interpolator;
	}
	
	/**
	 * 对Action进行校验，若对象字段有@Valid，则进行级联校验，只对存在于请求参数的字段/对象进行校验
	 * 
	 * @param actionAs ActionSupport对象
	 * @param clientLocale locale
	 * @param classLoader
	 * @return
	 * @throws IOException
	 */
	public List<ConstraintViolation<ActionSupport>> validateAction(ActionSupport actionAs, MessageInterpolator interpolator)
			throws IOException {
		// 违规收集器
		List<ConstraintViolation<ActionSupport>> invalidValuesFromRequest = new ArrayList<ConstraintViolation<ActionSupport>>();
		
		Validator validator = validatorFactory.usingContext().messageInterpolator(interpolator).getValidator();
		
		// 对整个Action进行校验
		Set<ConstraintViolation<ActionSupport>> constraintViolations = validator.validate(actionAs);
		
		List<Path> invalidFieldNames = new ArrayList<Path>();
		Map<String, Object> parameters = ActionContext.getContext().getParameters();
		// 过滤请求参数没有的"约束违规"
		// TODO: 需要修改
		for (ConstraintViolation<ActionSupport> constrantViolation : constraintViolations) {
			Path fieldPath = constrantViolation.getPropertyPath();
			Map<String, Object> attrs = constrantViolation.getConstraintDescriptor().getAttributes();
			if (invalidFieldNames.contains(fieldPath))
				continue;
			if (parameters.containsKey(fieldPath.toString())) {
				invalidValuesFromRequest.add(constrantViolation);
				invalidFieldNames.add(fieldPath);
			}
		}
		
		// 清理
		constraintViolations.clear();
		invalidFieldNames.clear();
		
		return invalidValuesFromRequest;
	}
}
